{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "from IPython.display import display, Markdown\n",
    "from datetime import time, datetime, date, timedelta\n",
    "import snakemd\n",
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.dates as dates\n",
    "\n",
    "import fitfile\n",
    "from garmindb import GarminConnectConfigManager\n",
    "from garmindb.garmindb import GarminDb, Attributes, ActivitiesDb, Activities, StepsActivities, ActivityLaps, ActivityRecords\n",
    "from idbutils.list_and_dict import list_not_none\n",
    "\n",
    "from jupyter_funcs import format_number\n",
    "\n",
    "gc_config = GarminConnectConfigManager()\n",
    "db_params_dict = gc_config.get_db_params()\n",
    "\n",
    "garmin_db = GarminDb(db_params_dict)\n",
    "garmin_act_db = ActivitiesDb(db_params_dict)\n",
    "measurement_system = Attributes.measurements_type(garmin_db)\n",
    "unit_strings = fitfile.units.unit_strings[measurement_system]\n",
    "distance_units = {\"kilometers\": \"km\"}[unit_strings[fitfile.units.UnitTypes.distance_long]]\n",
    "\n",
    "def __format_activity(activity):\n",
    "    if activity:\n",
    "        return [activity.activity_id, activity.name, activity.start_time.strftime(\"%y%m%d\"), activity.sport, format_number(activity.distance, 1), activity.elapsed_time, activity.moving_time, format_number(activity.avg_speed, 1), format_number(activity.calories), activity.training_load, activity.training_effect, activity.anaerobic_training_effect]\n",
    "    return ['', '', '', '', '', '', '', '', '', '', '', '']\n",
    "\n",
    "\n",
    "activities = Activities.get_latest(garmin_act_db, Activities.row_count(garmin_act_db))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def debug(name=None):\n",
    "    doc = snakemd.new_doc()\n",
    "    if (name is None):\n",
    "        rows = [__format_activity(activity) for activity in activities]\n",
    "    else:\n",
    "        rows = [__format_activity(activity) for activity in activities if ((activity.name is not None) and (name in activity.name.lower()))]\n",
    "    doc.add_heading(\"All Recorded Activities\", 3)\n",
    "    doc.add_table(['Id', 'Name', 'Date', 'Sport', f'Dist ({distance_units})', 'Elapsed Time', f'Moving Time', f'Speed ({unit_strings[fitfile.units.UnitTypes.speed]})', 'Calories', 'Exercise Load', 'Aerobic effect', 'Anaerobic effect'], rows)\n",
    "    display(Markdown(str(doc)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def __plot_activity(name:str, ydata:str, cdata:str, ylimdata = None, yticksdata:tuple[float, float] = None):\n",
    "    start_date_array = []\n",
    "    moving_time_array = []\n",
    "    distance_array = []\n",
    "    calorie_array = []\n",
    "    time100m_array = []\n",
    "    time1000m_array = []\n",
    "    speed_array = []\n",
    "    training_load_array = []\n",
    "    training_effect_array = []\n",
    "    anaerobic_training_effect_array = []\n",
    "    for activity in activities:\n",
    "        if ((activity.name is not None) and (name in activity.name.lower())):\n",
    "            s = activity.distance\n",
    "            t = (datetime.combine(date.min, activity.moving_time) - datetime.min).total_seconds()\n",
    "            if (s > 0):\n",
    "                time100m_array += [t/s/10]\n",
    "                time1000m_array += [t/s]\n",
    "            else:\n",
    "                time100m_array += [0]\n",
    "                time1000m_array += [0]\n",
    "            start_date_array += [activity.start_time]\n",
    "            moving_time_array += [t/60] # Convert seconds to minutes\n",
    "            calorie_array += [activity.calories]\n",
    "            distance_array += [s]\n",
    "            speed_array += [activity.avg_speed]\n",
    "            training_load_array += [activity.training_load]\n",
    "            training_effect_array += [activity.training_effect]\n",
    "            anaerobic_training_effect_array += [activity.anaerobic_training_effect]\n",
    "\n",
    "    yarray = []\n",
    "    carray = []\n",
    "    match ydata:\n",
    "        case \"Moving Time\": yarray = moving_time_array\n",
    "        case \"Calorie\": yarray = calorie_array\n",
    "        case \"Distance\": yarray = distance_array\n",
    "        case \"100m Time\": yarray = time100m_array\n",
    "        case \"1k Time\": yarray = time1000m_array\n",
    "        case \"Speed\": yarray = speed_array\n",
    "        case \"Exercise Load\": yarray = training_load_array\n",
    "        case \"Training Effect\": yarray = training_effect_array\n",
    "        case \"Anaerobic Training Effect\": yarray = anaerobic_training_effect_array\n",
    "    match cdata:\n",
    "        case \"Moving Time\": carray = moving_time_array\n",
    "        case \"Calorie\": carray = calorie_array\n",
    "        case \"Distance\": carray = distance_array\n",
    "        case \"100m Time\": carray = time100m_array\n",
    "        case \"1k Time\": carray = time1000m_array\n",
    "        case \"Speed\": carray = speed_array\n",
    "        case \"Exercise Load\": carray = training_load_array\n",
    "        case \"Training Effect\": carray = training_effect_array\n",
    "        case \"Anaerobic Training Effect\": carray = anaerobic_training_effect_array\n",
    "\n",
    "    fig = plt.figure(figsize=(20,5), dpi= 100, facecolor='w', edgecolor='k')\n",
    "    plt.scatter(start_date_array, yarray, c=carray, cmap=\"rainbow\")\n",
    "\n",
    "    # First remove any NaN/inf values\n",
    "    x_num = dates.date2num(start_date_array)\n",
    "    x_num = np.array(x_num)\n",
    "    yarray = np.array(yarray)\n",
    "    carray = np.array(carray)\n",
    "    valid_idx = np.isfinite(x_num) & np.isfinite(yarray)\n",
    "    x_clean, y_clean = x_num[valid_idx], yarray[valid_idx]\n",
    "\n",
    "    # Filter outliers before trend calculation\n",
    "    initial_trend = np.polyfit(x_clean, y_clean, 3)\n",
    "    initial_fit = np.poly1d(initial_trend)\n",
    "    residuals = y_clean - initial_fit(x_clean)\n",
    "\n",
    "    # Keep 95% of points closest to the trend\n",
    "    residual_threshold = np.percentile(np.abs(residuals), 95)\n",
    "    mask = np.abs(residuals) <= residual_threshold\n",
    "\n",
    "    # Fit final trend on filtered data\n",
    "    trend = np.polyfit(x_num[mask], yarray[mask], 3)\n",
    "    fit = np.poly1d(trend)\n",
    "    x_fit = np.linspace(x_num.min(), x_num.max())\n",
    "    plt.plot(dates.num2date(x_fit), fit(x_fit), \"r--\")\n",
    "\n",
    "    ax = plt.gca()\n",
    "    plt.grid(visible=True, which='major', color='#666666', linestyle='-')\n",
    "    plt.grid(visible=True, which='minor', color='#999999', linestyle='-', alpha=0.2)\n",
    "    years = plt.matplotlib.dates.YearLocator()\n",
    "    months = plt.matplotlib.dates.MonthLocator()\n",
    "    # yearsFmt = plt.matplotlib.dates.DateFormatter('%Y')\n",
    "    ax.xaxis.set_major_locator(years)\n",
    "    ax.xaxis.set_minor_locator(months)\n",
    "    # ax.xaxis.set_major_formatter(yearsFmt)\n",
    "\n",
    "    plt.title(f\"{ydata} across {len(start_date_array)} {name} sessions\")\n",
    "    plt.colorbar(label=cdata)\n",
    "    plt.ylabel(ydata)\n",
    "    if ylimdata is not None:\n",
    "        plt.ylim(ylimdata)\n",
    "    if yticksdata is not None:\n",
    "        plt.yticks(yticksdata)\n",
    "    plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "__plot_activity(\"swim\", \"100m Time\", \"Distance\")\n",
    "__plot_activity(\"swim\", \"Distance\", \"100m Time\", ylimdata=[0, 1])\n",
    "__plot_activity(\"run\", \"1k Time\", \"Exercise Load\", yticksdata=[300, 360, 420, 480], ylimdata=[300, 480])\n",
    "__plot_activity(\"run\", \"Distance\", \"1k Time\", ylimdata=[2, 10.3])\n",
    "__plot_activity(\"strength\", \"Exercise Load\", \"Moving Time\")\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
